﻿/*
    This file is part of nDoctor Light

    NDoctor is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    NDoctor is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with NDoctor.  If not, see <http://www.gnu.org/licenses/>.
*/
namespace Probel.Ldoc.Views.Pages
{
    using System;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Threading;

    using Probel.Ldoc.Services.Dto;
    using Probel.Ldoc.Shell.Tools;
    using Probel.Ldoc.ViewModels.Dto;
    using Probel.Ldoc.ViewModels.Pages;
    using Probel.Ldoc.ViewModels.ToolBoxes;
    using Probel.Ldoc.Views.Properties;
    using Probel.Ldoc.Views.ToolBoxes;
    using Probel.Mvvm.DataBinding;
    using Probel.Mvvm.Gui;

    /// <summary>
    /// Interaction logic for RecordEditorView.xaml
    /// </summary>
    public partial class RecordEditorView : Page, ISavable
    {
        #region Fields

        private readonly SolidColorBrush HighlightColour = Brushes.Yellow;
        private readonly EditorTextStatus TextStatus = new EditorTextStatus();

        #endregion Fields

        #region Constructors

        public RecordEditorView(RecordEditorViewModel viewmodel)
        {
            InitializeComponent();

            ViewService.RootWindow.Closing += (sender, e) => e.Cancel = this.ManageCloseRecord(this.treeView.SelectedItem as RecordCardViewModel);

            this.DataContext = viewmodel;
            this.cbFont.ItemsSource = Fonts.SystemFontFamilies.OrderBy(f => f.Source);

            this.FillMacroContextMenu();
        }

        #endregion Constructors

        #region Properties

        /// <summary>
        /// Gets a value indicating whether this instance has been modified.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance is modified; otherwise, <c>false</c>.
        /// </value>
        public bool IsModified
        {
            get { return this.TextStatus.HasChanged && !this.TextStatus.IsLoading; }
        }

        private bool IsHighlitghting
        {
            get;
            set;
        }

        private RecordEditorViewModel ViewModel
        {
            get
            {
                return this.DataContext as RecordEditorViewModel;
            }
        }

        #endregion Properties

        #region Methods

        /// <summary>
        /// Saves the unchanged data of this instance.
        /// </summary>
        /// <returns>
        ///   <c>True</c> if navigation should be cancelled. Otherwise <c>False</c>
        /// </returns>
        public bool ManageSaveFlow()
        {
            return this.ManageCloseRecord(this.treeView.SelectedItem as RecordCardViewModel);
        }

        private static TreeViewItem VisualUpwardSearch(DependencyObject source)
        {
            while (source != null && !(source is TreeViewItem))
                source = VisualTreeHelper.GetParent(source);

            return source as TreeViewItem;
        }

        private void AddRecord_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = this.treeView.SelectedItem is RecordDrawerViewModel;
        }

        private void AddRecord_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            var drawer = this.treeView.SelectedItem as RecordDrawerViewModel;
            var cmd = this.ViewModel.AddNewRecordCommand;

            if (drawer != null && cmd.CanExecute(drawer.Id))
            {
                cmd.Execute(drawer.Id);
            }
            else { this.ViewModel.UiMessage = UiMessageDto.Error(Messages.Msg_AddRecordImpossible); }
        }

        private void cbFont_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (this.cbFont.SelectedItem != null)
            {
                this.editor.Selection.ApplyPropertyValue(RichTextBox.FontFamilyProperty, this.cbFont.SelectedItem);
            }
        }

        private void cbSize_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (this.cbSize.SelectedItem != null)
            {
                this.editor.Selection.ApplyPropertyValue(RichTextBox.FontSizeProperty, this.cbSize.SelectedItem);
            }
        }

        private void Click_CreateFolder(object sender, RoutedEventArgs e)
        {
            var view = new CreateFolderView(this.ViewModel.BuildFolderViewModel());
            view.Owner = ViewService.RootWindow;
            var ok = view.ShowDialog();
        }

        private void Click_CreateRecord(object sender, RoutedEventArgs e)
        {
            var view = new CreateFileView(this.ViewModel.BuildFileViewModel());
            view.Owner = ViewService.RootWindow;
            var ok = view.ShowDialog();
        }

        private void Click_HighlightText(object sender, RoutedEventArgs e)
        {
            if (this.editor.Selection.IsEmpty) { this.IsHighlitghting = true; }
            else { this.HighlightSelectedText(); }
        }

        private void Click_RenameRecord(object sender, RoutedEventArgs e)
        {
            if (this.treeView.SelectedItem != null)
            {
                var vm = this.ViewModel.BuildRenameRecordViewModel();
                var view = new RenameRecordView(vm);
                view.Owner = ViewService.RootWindow;
                view.ShowDialog();

                var selectedItem = this.treeView.SelectedItem as RecordCardViewModel;
                this.ViewModel.Refresh();
                if (selectedItem != null)
                {
                    selectedItem.RefreshTitle();
                }
            }
            else { ViewService.MessageBox.Warning(Messages.Msg_NoRecordSelected); }
        }

        private void Click_ShowHistory(object sender, RoutedEventArgs e)
        {
            if (this.ViewModel.CurrentRecord != null)
            {
                var viewmodel = this.ViewModel.BuildRecordHistoryViewModel();
                viewmodel.Refresh();

                var view = new RecordHistoryView(viewmodel);
                view.Owner = ViewService.RootWindow;
                view.ShowDialog();

                this.LoadRecord(this.treeView.SelectedItem as RecordCardViewModel);

            }
            else { ViewService.MessageBox.Warning(Messages.Msg_NoRecordSelected); }
        }

        private void Click_ShowMacroEditor(object sender, RoutedEventArgs e)
        {
            var viewmodel = this.ViewModel.BuildMacroEditorViewModel();
            viewmodel.Refresh();

            var view = new MacroEditorView(viewmodel);
            view.Owner = ViewService.RootWindow;
            view.ShowDialog();

            this.FillMacroContextMenu();
        }

        private void Editor_MouseUp(object sender, MouseButtonEventArgs e)
        {
            if (this.IsHighlitghting && !this.editor.Selection.IsEmpty)
            {
                this.HighlightSelectedText();
            }
        }

        private void Editor_SelectionChanged(object sender, RoutedEventArgs e)
        {
            object temp = this.editor.Selection.GetPropertyValue(Inline.FontFamilyProperty);
            this.cbFont.SelectedItem = temp;
            temp = editor.Selection.GetPropertyValue(Inline.FontSizeProperty);
            this.cbSize.Text = temp.ToString();
        }

        private void Editor_TextChanged(object sender, TextChangedEventArgs e)
        {
            if (!this.TextStatus.IsLoading) { this.TextStatus.ForceAsChanged(); }
            else { this.TextStatus.SetAsLoaded(); }
        }

        private void FillMacroContextMenu()
        {
            var macros = this.ViewModel.GetMacros();

            if (macros.Count() > 0)
            {
                this.macrosMenu.Items.Clear();
                this.macrosMenu.Visibility
                    = this.macrosSeparator.Visibility
                    = Visibility.Visible;

                foreach (var macro in macros)
                {
                    var menuItem = new MenuItem() { Header = macro.Title };
                    menuItem.Click += (sender, e) => this.InsertMacro(macro.Expression);
                    this.macrosMenu.Items.Add(menuItem);
                }
            }
            else
            {
                this.macrosMenu.Visibility
                 = this.macrosSeparator.Visibility
                 = Visibility.Collapsed;
            }
        }

        private string GetRtfFromEditor()
        {
            if (this.editor != null)
            {
                TextRange tr = new TextRange(this.editor.Document.ContentStart
                                           , this.editor.Document.ContentEnd);
                MemoryStream stream = new MemoryStream();
                tr.Save(stream, DataFormats.Rtf);
                string rtf = ASCIIEncoding.Default.GetString(stream.ToArray());
                return rtf;
            }
            else { return string.Empty; }
        }

        private void HighlightSelectedText()
        {
            var selection = new TextRange(this.editor.Selection.Start, this.editor.Selection.End);

            if (selection.GetPropertyValue(TextElement.BackgroundProperty) == this.HighlightColour)
            {
                selection.ApplyPropertyValue(TextElement.BackgroundProperty, Brushes.White);
            }
            else
            {
                selection.ApplyPropertyValue(TextElement.BackgroundProperty, this.HighlightColour);
            }

            this.IsHighlitghting = false;
            this.highlightButton.IsChecked = false;
        }

        private void InsertMacro(string text)
        {
            this.editor.CaretPosition.InsertTextInRun(Environment.NewLine); //To trigger text changed
            this.editor.Undo();

            this.editor.Selection.Text = text;
        }

        private void LoadRecord(RecordCardViewModel recordCard)
        {
            this.editor.ClearUndoStack();
            this.ViewModel.CurrentRecord = recordCard;
            var rtf = this.ViewModel.GetRecordRtf();

            if (rtf != null && rtf.Length > 0)
            {
                this.editor.IsEnabled = true;
                this.editor.Document.Blocks.Clear();
                var stream = new MemoryStream(ASCIIEncoding.Default.GetBytes(rtf));
                this.editor.Selection.Load(stream, DataFormats.Rtf);

                //Empty the text selection
                this.editor.Selection.Select(this.editor.Document.ContentEnd, this.editor.Document.ContentEnd);
            }

            else
            {
                this.editor.IsEnabled = true;
                this.editor.Document.Blocks.Clear();
            }
        }

        /// <summary>
        /// Ask whether user want to save or discard changes
        /// </summary>
        /// <returns>
        /// Returns always <c>True</c>. The user cannot cancel the action. The user can anly chose to save or not save
        /// before leaving. As there's a save history, user has to save and rollback if needed.
        /// </returns>
        private bool ManageCloseRecord(RecordCardViewModel record)
        {
            if (this.TextStatus.IsLoading || !this.TextStatus.HasChanged) { return false; }

            var answer = ViewService.MessageBox.Question(Messages.Msg_AskSaveRecord);

            if (answer) /* Yes click */ { this.SaveRecord(record); }
            return false;
        }

        private void Page_Loaded(object sender, RoutedEventArgs e)
        {
            this.ViewModel.Refresh();
        }

        private void RemoveRecord_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            if (this.ViewModel.RemoveRecordCommand.CanExecute(null))
            {
                this.ViewModel.RemoveRecordCommand.Execute(null);
                this.editor.Document.Blocks.Clear();
                this.TextStatus.ForceAsUnchanged();
                this.editor.IsEnabled = false;
            }
        }

        private void SaveCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            this.SaveRecord(this.treeView.SelectedItem as RecordCardViewModel);
        }

        private void SaveCommand__CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            var record = this.treeView.SelectedItem as RecordCardViewModel;
            e.CanExecute = (record != null);
        }

        private void SaveRecord(RecordCardViewModel record)
        {
            if (record != null)
            {
                var rtf = this.GetRtfFromEditor();

                if (this.treeView.SelectedItem is RecordCardViewModel && !string.IsNullOrEmpty(rtf))
                {
                    Mouse.OverrideCursor = Cursors.Wait;
                    this.ViewModel.CurrentRecord = record;
                    this.ViewModel.SaveRecordAsync(rtf, () => Mouse.OverrideCursor = Cursors.Arrow);
                }
                this.TextStatus.SetAsJustSaved();
            }
            else { this.ViewModel.UiMessage = UiMessageDto.Warn(Messages.Msg_NoRecordToBeSaved); }
        }

        private void TextBox_GotFocus(object sender, RoutedEventArgs e)
        {
            if (sender is TextBox)
            {
                var textBox = sender as TextBox;
                this.Dispatcher.BeginInvoke((Action)delegate { Keyboard.Focus(textBox); }, DispatcherPriority.Render);
                textBox.Select(0, textBox.Text.Length);
            }
        }

        private void TreeView_PreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
        }

        private void TreeView_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            TreeViewItem treeViewItem = VisualUpwardSearch(e.OriginalSource as DependencyObject);

            if (treeViewItem != null)
            {
                treeViewItem.Focus();
                e.Handled = true;
            }
        }

        private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            //Deactivate edition mode of previous selected node if it was in edition mode
            if (e.OldValue != null && e.OldValue is TreeViewModel)
            {
                var oldValue = e.OldValue as TreeViewModel;
                if (oldValue.IsEditing == true) { oldValue.IsEditing = false; }
            }

            //Load medical record of the selected node if it is a record node (and not a folder node)
            if (e.NewValue == null) { this.editor.Document.Blocks.Clear(); }
            else if (e.NewValue is RecordCardViewModel)
            {

                this.ManageCloseRecord(e.OldValue as RecordCardViewModel);

                Mouse.OverrideCursor = Cursors.Wait;
                this.LoadRecord(e.NewValue as RecordCardViewModel);
                this.TextStatus.SetAsLoading();
                Mouse.OverrideCursor = Cursors.Arrow;

                this.editor.ScrollToEnd();
            }
        }

        #endregion Methods

        #region Nested Types

        private class EditorTextStatus
        {
            #region Constructors

            public EditorTextStatus()
            {
                this.IsLoading = true;
                this.HasChanged = false;
            }

            #endregion Constructors

            #region Properties

            public bool HasChanged
            {
                get;
                private set;
            }

            public bool IsLoading
            {
                get;
                private set;
            }

            #endregion Properties

            #region Methods

            public void ForceAsChanged()
            {
                this.IsLoading = false;
                this.HasChanged = true;
            }

            public void ForceAsUnchanged()
            {
                this.HasChanged = false;
            }

            public void SetAsJustSaved()
            {
                this.IsLoading = false;
                this.HasChanged = false;
            }

            public void SetAsLoaded()
            {
                this.IsLoading = false;
                this.HasChanged = false;
            }

            public void SetAsLoading()
            {
                this.IsLoading = true;
                this.HasChanged = false;
            }

            #endregion Methods
        }

        #endregion Nested Types
    }

    internal static class RichTextBoxExtension
    {
        #region Methods

        public static void ClearUndoStack(this RichTextBox editor)
        {
            editor.UndoLimit = 0;
            editor.UndoLimit = -1;
        }

        #endregion Methods
    }
}